import Error from 'next/error';
import ReactMarkdown from 'react-markdown';
import { IconContext } from 'react-icons';
import { GoFile, GoFileDirectory, GoGitCommit, GoGitBranch, GoTag } from 'react-icons/go';
import gfm from 'remark-gfm';
import hljs from 'highlight.js';
import path from 'path';
function getRawURL(pathArray) {
return `/${pathArray[0]}/raw/${pathArray.slice(2).join('/')}`;
}
function getLanguage(filename) {
switch (path.extname(filename)) {
case '.java':
return 'java';
case '.html':
case '.xml':
return 'xml';
case '.md':
return 'md';
case '.c':
case '.h':
return 'c';
case '.tex':
return 'tex';
case '.js':
return 'js';
case '.fs':
return 'fs';
case '.elm':
return 'elm';
case '.json':
return 'json';
case '.py':
return 'py';
case '.css':
return 'css';
default:
return 'plaintext';
}
}
function isImage(filename) {
switch (path.extname(filename)) {
case '.gif':
case '.jpeg':
case '.jpg':
case '.png':
case '.tiff':
case '.tif':
return true;
default:
return false;
}
}
function makeBreadCrumbObj(name, pathArray) {
const breadCrumbObj = {
name: name,
href: `/${pathArray.join('/')}`,
};
return breadCrumbObj;
}
async function getServerSideProps(context) {
const res = await fetch(`http://localhost:3000/api${context.resolvedUrl}`);
const data = await res.json();
if (data.resultType === 'notFound') {
return { notFound: true };
} else {
return { props: { data } };
}
}
function Button({ label, url }) {
return (
<a className={'block px-4 py-1.5 border border-slate-300 rounded-lg'} href={url}>{label}</a>
);
}
function Link({ label, url }) {
return (
<a className={'text-blue-600 hover:underline'} href={url}>{label}</a>
);
}
function ListItemWithIcon({ icon, iconColor, name, url }) {
return (
<li className={'py-2.5 flex items-center'}>
<div className={'pr-4'}>
<IconContext.Provider value={{ size: '1.5em', className: iconColor }}>
{icon}
</IconContext.Provider>
</div>
<a className={'hover:text-blue-600 hover:underline'} href={url}>{name}</a>
</li>
);
}
function MainContentContainer({ children }) {
return (
<div className={'md:border md:border-slate-300 md:rounded-lg md:px-12 md:py-8'}>
{children}
</div>
);
}
function LineNos({ textBlob }) {
const count = textBlob.split('\n').length;
const lineNos = [...Array(count).keys()].map((x) => x + 1).join('\n');
return (
<pre className={'text-right text-slate-400'}>
{lineNos}
</pre>
);
}
function CodeBlock({ textBlob, resultPath }) {
const language = getLanguage(resultPath[resultPath.length - 1]);
const highlightedHTML =
hljs.highlight(textBlob, { language: language, ignoreIllegals: true }).value;
return (
<pre>
<code dangerouslySetInnerHTML={{ __html: highlightedHTML }} />
</pre>
);
}
function TextDisplayGrid({ textBlob, resultPath }) {
return (
<div className={'flex'}>
<div className={'overflow-x-auto'}>
<CodeBlock textBlob={textBlob} resultPath={resultPath} />
</div>
<div className={'pr-4 md:pr-8 order-first'}>
<LineNos textBlob={textBlob} />
</div>
</div>
);
}
function BreadCrumbTrail({ pathArray }) {
const recursion = (acc) => {
if (acc.length === pathArray.length - 4) {
return acc;
} else {
const subArray = pathArray.slice(0, 1).concat('tree', pathArray.slice(2, acc.length + 4));
const breadCrumb = makeBreadCrumbObj(pathArray[acc.length + 3], subArray);
const newAcc = acc.concat([breadCrumb]);
return recursion(newAcc);
}
};
if (pathArray.length < 4) {
return null;
}
let rootArray;
if (pathArray[2] === 'master') {
rootArray = pathArray.slice(0, 1);
} else {
rootArray = pathArray.slice(0, 1).concat('tree', pathArray[2]);
}
const headBreadCrumb = makeBreadCrumbObj(pathArray[0], rootArray);
const tailBreadCrumbs = recursion([]);
const breadCrumbs = [headBreadCrumb].concat(tailBreadCrumbs);
return (
<div className={'text-xl md:text-2xl py-2 md:py-3'}>
{breadCrumbs.map((breadCrumb, index) => (
<span key={index}> <Link label={breadCrumb.name} url={breadCrumb.href} /> /</span>
))}
<span> {pathArray[pathArray.length - 1]}</span>
</div>
);
}
function ReadmeDisplay({ resultContent, resultPath }) {
const imageURITransformer = (uri) => `/${resultPath[0]}/raw/master/${uri}`;
const titleClasses = 'text-2xl text-slate-800 pb-4 border-b border-slate-300';
const articleClasses = 'pt-4 prose md:prose-xl prose-slate max-w-none prose-a:text-blue-600 prose-a:no-underline hover:prose-a:underline';
return (
<div className={'mt-16'}>
<MainContentContainer>
<p className={titleClasses}><strong>{resultContent.readmeName}</strong></p>
<article className={articleClasses}>
<ReactMarkdown
remarkPlugins={[gfm]}
transformImageUri={imageURITransformer}
children={resultContent.readmeBlob}
/>
</article>
</MainContentContainer>
</div>
);
}
function TextBlobArea({ resultContent, resultPath }) {
return (
<MainContentContainer>
<div className={'flex justify-end pb-2'}>
<Button label={'Raw'} url={getRawURL(resultPath)} />
</div>
<TextDisplayGrid textBlob={resultContent} resultPath={resultPath} />
</MainContentContainer>
);
}
function BinaryBlobArea({ resultPath }) {
let binaryDisplay;
if (isImage(resultPath[resultPath.length - 1])) {
binaryDisplay = <img src={getRawURL(resultPath)} alt={'Embedded Image.'} />;
} else {
binaryDisplay = <p className={'text-center text-xl'}>Binary content cannot be displayed.</p>;
}
return (
<MainContentContainer>
<div className={'flex justify-end pb-2'}>
<Button label={'Download'} url={getRawURL(resultPath)} />
</div>
{binaryDisplay}
</MainContentContainer>
);
}
function BranchesAndTagsLinks({ pathArray }) {
const commit = pathArray.length > 2 ? pathArray[2] : 'master';
return (
<div className={'text-xl md:text-2xl py-2 md:py-3'}>
<Link label={'Branches'} url={`/${pathArray[0]}/branches`} />
<span className={'px-2 md:px-4'} />
<Link label={'Tags'} url={`/${pathArray[0]}/tags`} />
<span className={'px-2 md:px-4'} />
<Link label={'History'} url={`/${pathArray[0]}/commits/${commit}`} />
</div>
);
}
function MidSection({ resultPath }) {
const commit = resultPath.length > 2 ? resultPath[2] : 'master';
let navigation;
if (resultPath.length > 3) {
navigation = <BreadCrumbTrail pathArray={resultPath} />;
} else {
navigation = <BranchesAndTagsLinks pathArray={resultPath} />;
}
return (
<div className={'py-3 md:py-5 px-2 md:px-4 flex flex-wrap items-center'}>
{resultPath.length !== 2 &&
(
<div className={'text-xl md:text-2xl py-2 px-4 mr-4 md:mr-8 border border-slate-300 rounded-lg truncate'}>{commit}</div>
)}
{navigation}
</div>
);
}
function DirectoryListing({ resultContent, resultPath }) {
const makeURL = (repoRequest, item) => {
const parentTail = resultPath.length > 2 ? resultPath.slice(2) : ['master'];
const parentPath = resultPath.slice(0, 1).concat(repoRequest, parentTail);
const url = `/${parentPath.join('/')}/${item}`;
return url;
}
return (
<MainContentContainer>
<ul className={'text-lg divide-y divide-slate-300'}>
{resultContent.trees.map((item, index) => (
<ListItemWithIcon
key={index}
icon={<GoFileDirectory />}
iconColor={'text-blue-400'}
name={item}
url={makeURL('tree', item)}
/>
))}
{resultContent.blobs.map((item, index) => (
<ListItemWithIcon
key={index}
icon={<GoFile />}
iconColor={'text-slate-500'}
name={item}
url={makeURL('blob', item)}
/>
))}
</ul>
</MainContentContainer>
);
}
function CommitListing({ resultContent, resultPath }) {
return (
<MainContentContainer>
<ul className={'text-lg divide-y divide-slate-300'}>
{resultContent.map((item, index) => (
<ListItemWithIcon
key={index}
icon={<GoGitCommit />}
iconColor={'text-slate-500'}
name={item.message}
url={`/${resultPath[0]}/tree/${item.id}`}
/>
))}
</ul>
</MainContentContainer>
);
}
function BranchListing({ resultContent, resultPath }) {
return (
<MainContentContainer>
<ul className={'text-lg divide-y divide-slate-300'}>
<ListItemWithIcon
icon={<GoGitBranch />}
iconColor={'text-slate-500'}
name={'master'}
url={`/${resultPath[0]}`}
/>
{resultContent.filter((x) => x !== 'master').map((item, index) => (
<ListItemWithIcon
key={index}
icon={<GoGitBranch />}
iconColor={'text-slate-500'}
name={item}
url={`/${resultPath[0]}/tree/${item}`}
/>
))}
</ul>
</MainContentContainer>
);
}
function TagListing({ resultContent, resultPath }) {
return (
<MainContentContainer>
{resultContent.length === 0 &&
(
<p className={'text-center text-xl'}>There are no tags in this project.</p>
)}
<ul className={'text-lg divide-y divide-slate-300'}>
{resultContent.reverse().map((item, index) => (
<ListItemWithIcon
key={index}
icon={<GoTag />}
iconColor={'text-slate-500'}
name={item}
url={`/${resultPath[0]}/tree/${item}`}
/>
))}
</ul>
</MainContentContainer>
);
}
function RepositoryListing({ repositories }) {
return (
<ul className={'text-xl md:text-2xl divide-y divide-slate-300'}>
{repositories.map((repo, index) => (
<li key={index} className={'p-4 md:p-6'}>
<Link label={repo.name} url={`/${repo.name}`} />
</li>
))}
</ul>
);
}
function PageSubstance({ data }) {
switch (data.resultType) {
case 'home':
return <RepositoryListing repositories={data.resultContent} />;
break;
case 'rootDirectory':
return (
<div>
<MidSection resultPath={data.resultPath} />
<DirectoryListing
resultContent={data.resultContent.entryNamesSorted}
resultPath={data.resultPath}
/>
{data.resultContent.readmeExists && (
<ReadmeDisplay
resultContent={data.resultContent}
resultPath={data.resultPath}
/>
)}
</div>
);
break;
case 'nonRootDirectory':
return (
<div>
<MidSection resultPath={data.resultPath} />
<DirectoryListing
resultContent={data.resultContent}
resultPath={data.resultPath}
/>
</div>
);
break;
case 'textBlob':
return (
<div>
<MidSection resultPath={data.resultPath} />
<TextBlobArea
resultContent={data.resultContent}
resultPath={data.resultPath}
/>
</div>
);
break;
case 'binaryBlob':
return (
<div>
<MidSection resultPath={data.resultPath} />
<BinaryBlobArea resultPath={data.resultPath} />
</div>
);
break;
case 'commits':
return (
<div>
<MidSection resultPath={data.resultPath} />
<CommitListing
resultContent={data.resultContent}
resultPath={data.resultPath}
/>
</div>
);
break;
case 'branches':
return (
<div>
<MidSection resultPath={data.resultPath} />
<BranchListing
resultContent={data.resultContent}
resultPath={data.resultPath}
/>
</div>
);
break;
case 'tags':
return (
<div>
<MidSection resultPath={data.resultPath} />
<TagListing
resultContent={data.resultContent}
resultPath={data.resultPath}
/>
</div>
);
break;
}
}
function Header({ resultPath }) {
return (
<header className={'p-5 bg-slate-100 border-b border-slate-300'}>
<div className={'md:pl-8 text-2xl md:text-5xl break-words'}>
<a className={'text-blue-600 hover:underline font-mono'} href={'/'}>cleanslatesoftware.com</a>
{resultPath.length > 0 && (
<span> / <Link label={resultPath[0]} url={`/${resultPath[0]}`} /></span>
)}
</div>
</header>
);
}
function Footer() {
return (
<footer className={'py-16 px-3 text-lg md:text-xl break-words'}>
<div className={'text-center'}>Email: dean@cleanslatesoftware.com</div>
<div className={'text-center pt-6'}>Copyright © 2023 Dean Lee</div>
</footer>
);
}
function Main({ data }) {
switch (data.resultType) {
case 'home':
case 'rootDirectory':
case 'nonRootDirectory':
case 'textBlob':
case 'binaryBlob':
case 'commits':
case 'branches':
case 'tags':
return (
<div>
<Header resultPath={data.resultPath} />
<main>
<div className={'container mx-auto px-3 md:px-8'}>
<PageSubstance data={data} />
</div>
</main>
<Footer />
</div>
);
break;
default:
return <Error statusCode={'500'} title={'Internal server error, try reloading the page'} />;
}
}
export { getServerSideProps };
export default Main;